import logging as logger

from pythontojavascript import methodbuilder

OBJECT_CACHE = {}
OBJECT_NAME_CACHE = {}
AUTHORIZED_METHODS = [None] # None is authorized, because it means "create obj"
AUTHORIZED_MODULES = []

class CompilationError(Exception): pass

def compile(mode="default"):
  """
  For all the objects in the cache, check what module they want to be compiled 
  with, grab that module, compile the object. If no module specified, use the 
  passed-in module.
  """
  obj_js = []
  compiler_js = []
  modules = {}
  for obj in getObjects():
    obj_mode = OBJECT_CACHE[obj]['mode'] or mode
    try:
      mod = __import__("pythontojavascript.compilers."+obj_mode,[],[],[mode])
      modules[obj_mode] = mod
      logger.debug("Imported compile module: %s" % obj_mode)
    except ImportError:
      # Do something here?
      raise
    try:  
      obj_js.append(mod.compile(obj))
    except Exception,e:
      logger.exception("Compilation error:")
      raise CompilationError("Error compiling object %s with mode %s: %s" % 
                             (obj,obj_mode,e))
  # Add any templates that the individual compilers need
  print "Modules:",modules.keys()
  for mode in modules.keys():
    compiler_js.append(modules[mode].template())
  
  js = []
  js.extend(compiler_js)
  js.extend(obj_js)
  return "\n".join(js)

def addObjects(objs,mode=None):
  for obj in objs:
    addObject(obj,mode)

def addObject(obj,mode=None):
  """
  Add an object to the cache.
  mode tells the compiler what mode to compile this obj in.
  """
  if not OBJECT_CACHE.has_key(obj):
    OBJECT_CACHE[obj] = {"InstanceMethods":[],
                         "ClassMethods":[],
                         "Properties":[],
                         "mode":mode,
                        }
    OBJECT_NAME_CACHE[obj.__name__] = obj
    
def addProperty(cls,prop,sync=True,inside="",cache=True,skipcache=False,
                isproperty=True):
  """
  Properties are a bit different from methods - notably, we don't know
  where they came from :(
  Since JS doesn't have "properties" like Python, we make 'em instance methods.
  They differ from pure "instance" methods however, because we precache them
  when the object is created.
  """
  assert type(prop) in (str,unicode),"addProperty sucks - it needs a name."
  obj = cls
  method_name = prop
  addObject(obj)
  OBJECT_CACHE[obj]["Properties"].append(prop)
  inst_method = methodbuilder.JSInstanceMethod.fromProperty(
      MethodName=method_name,sync=sync,MethodGuts=inside,cache=cache,
      skipcache=skipcache,isproperty=isproperty)
  #OBJECT_CACHE[obj]["InstanceMethods"].append(inst_method)
  
def addInstMethodByName(obj,MethodName,**kwargs):
  """
  We don't add the Instance Method if there's already a property with the same
  name, because it would overwrite it.
  """
  addObject(obj)
  properties = getProperties(obj)
  if not MethodName in properties:
    inst_method = methodbuilder.JSInstanceMethod(MethodName=MethodName,**kwargs)
    OBJECT_CACHE[obj]["InstanceMethods"].append(inst_method)

def addInstMethod(method,**kwargs):
  """
  Takes an instance method, like "foo.bar", and adds it to the compiler cache
  """
  obj = method.im_class
  method_name = method.__name__
  addInstMethodByName(obj,method_name,**kwargs)

def addClassMethodByName(obj,MethodName,sync=False):
  addObject(obj)
  cls_method = methodbuilder.JSClassMethod(MethodName=MethodName,sync=sync)
  OBJECT_CACHE[obj]["ClassMethods"].append(cls_method)

def addClassMethod(method,sync=False):
  """
  Takes a class method, like "Jurist.Preload", and adds it to compiler the cache
  """
  obj = method.im_self
  method_name = method.__name__
  addClassMethodByName(obj,method_name,sync)
  
def getObjects():
  return OBJECT_CACHE.keys()
  
def getProperties(obj):
  return OBJECT_CACHE[obj]["Properties"]

def getInstMethods(obj):
  return OBJECT_CACHE[obj]["InstanceMethods"]

def getClassMethods(obj):
  return OBJECT_CACHE[obj]["ClassMethods"]

def getObjectByName(name):
  return OBJECT_NAME_CACHE.get(name)
  
def getAuthorizedMethods(obj=None):
  auth = []
  if obj:
    inst_methods = [ m.MethodName for m in getInstMethods(obj) ]
    cls_methods  = [ m.MethodName for m in getClassMethods(obj) ]
    auth.extend(inst_methods)
    auth.extend(cls_methods)
  auth.extend(AUTHORIZED_METHODS)
  return auth

def addAuthorizedMethod(name):
  AUTHORIZED_METHODS.append(name)

def getAuthorizedModules():
  return AUTHORIZED_MODULES
  
def addAuthorizedModule(name):
  """Add a module 'tree' to the authorized list. This allows us to import it 
  from Javascript later.
  
  We don't allow the "empty string" because that matches all modules.
  """
  AUTHORIZED_MODULES.append(name)